package org.tulkas.robot.extension;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.tulkas.robot.base.TulkasApplicationContext;
import org.tulkas.robot.exception.SystemException;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
@Service
public class ExtensionManager {
	@Autowired
	private TulkasApplicationContext appContext;

	private Map<String, Set<ConfigExtension>> configExtensions = new HashMap<String, Set<ConfigExtension>>();

	public IExtensionPoint[] getAllExtensionPoints() {
		Map<String, IExtensionPoint> map = appContext
				.getBeansOfType(IExtensionPoint.class);
		return map.values().toArray(new IExtensionPoint[map.size()]);
	}

	@SuppressWarnings("unchecked")
	public IJavaExtensionPoint<? extends IExtension>[] getJavaExtensionPoints() {
		Map<String, IJavaExtensionPoint> map = appContext
				.getBeansOfType(IJavaExtensionPoint.class);
		return map.values().toArray(new IJavaExtensionPoint<?>[map.size()]);
	}

	public IConfigExtensionPoint[] getConfigExtensionPoints() {
		Map<String, IConfigExtensionPoint> map = appContext
				.getBeansOfType(IConfigExtensionPoint.class);
		return map.values().toArray(new IConfigExtensionPoint[map.size()]);
	}

	@SuppressWarnings("unchecked")
	public <T extends IExtension> T[] getExtensions(
			IJavaExtensionPoint<T> extPoint) {
		Map<String, T> map = appContext.getBeansOfType(extPoint
				.getExtensionClass());
		T[] ret = null;
		try {
			ret = (T[]) Array.newInstance(extPoint.getExtensionClass(), map
					.size());
			return map.values().toArray(ret);
		} catch (NegativeArraySizeException e) {
			throw new SystemException(e);
		}
	}

	public <T extends IExtension> T[] getExtensions(
			Class<? extends IJavaExtensionPoint<T>> extPointCls) {
		IJavaExtensionPoint<T> point = appContext.getBean(extPointCls);
		if (point != null) {
			return getExtensions(point);
		} else {
			throw new SystemException("Extension point in class ["
					+ extPointCls.getName()
					+ "] not existing in application context!");
		}
	}

	public synchronized ConfigExtension[] getExtensions(
			IConfigExtensionPoint extPoint) {
		Set<ConfigExtension> set = configExtensions.get(extPoint.getId());
		if (set != null) {
			return set.toArray(new ConfigExtension[set.size()]);
		} else {
			return new ConfigExtension[0];
		}
	}

	public ConfigExtension[] getExtensions(
			Class<? extends IConfigExtensionPoint> extPointCls) {
		IConfigExtensionPoint point = appContext.getBean(extPointCls);
		if (point != null) {
			return getExtensions(point);
		} else {
			return new ConfigExtension[0];
		}
	}

	public IExtension[] getExtensions(IExtensionPoint extPoint) {
		if (extPoint instanceof IJavaExtensionPoint<?>) {
			return getExtensions((IJavaExtensionPoint<?>) extPoint);
		} else if (extPoint instanceof IConfigExtensionPoint) {
			return getExtensions((IConfigExtensionPoint) extPoint);
		}
		return new IExtension[0];
	}

	public IExtension[] getExtensions(String extPointId) {
		Map<String, IExtensionPoint> map = appContext
				.getBeansOfType(IExtensionPoint.class);
		Iterator<Entry<String, IExtensionPoint>> it = map.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, IExtensionPoint> entry = it.next();
			if (extPointId.equals(entry.getValue().getId())) {
				return getExtensions(entry.getValue());
			}
		}
		return new IExtension[0];
	}

	public synchronized void registerConfigExtension(String extPointId,
			ConfigExtension extension) {
		Set<ConfigExtension> set = configExtensions.get(extPointId);
		if (set == null) {
			set = new HashSet<ConfigExtension>();
			configExtensions.put(extPointId, set);
		}
		set.add(extension);
	}

}
